home *** CD-ROM | disk | FTP | other *** search
/ Clickx 65 / Clickx 65.iso / software / internet / xmarks / xmarks-3.1.1.xpi / chrome / content / foxmarks-server.js < prev    next >
Encoding:
JavaScript  |  2009-05-05  |  53.4 KB  |  1,642 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-server.js: component that implements the logical interface to
  5.  the server.
  6.  
  7.  */
  8.  
  9. // TO DO:
  10. // * Deal with failure to retrieve baseline state from server -> merge
  11.  
  12.  
  13.  
  14. function getDatasourceAttribute(synctype, attr){
  15.     switch(synctype){
  16.         case 'bookmarks':
  17.             switch(attr){
  18.                 case 'engine':
  19.                     return BookmarkDatasource.STORAGE_ENGINE;
  20.                 default:
  21.                     throw("getDatasourceAttribute: unknown attribute (" + attr + ")");
  22.             }
  23.             break;
  24.         case 'lastmodifieddate':
  25.             break;
  26.         default:
  27.             throw("getDatasourceAttribute: unknown synctype (" + synctype + ")");
  28.     }
  29.  
  30. }
  31.  
  32. function hasPasswordSync(){
  33.     return "@mozilla.org/login-manager;1" in Cc;
  34.  
  35. }
  36.  
  37. var SYNCSET = {};
  38. if(hasPasswordSync()){
  39.     SYNCSET = {
  40.         "bookmarks": BookmarkDatasource,
  41.         "passwords": PasswordDatasource
  42.     };
  43. }
  44. else {
  45.  
  46.     SYNCSET = {
  47.         "bookmarks": BookmarkDatasource
  48.     };
  49. }
  50.    
  51. function createDatasource(type){
  52.     var model = new SYNCSET[type];
  53.     if(model === undefined)
  54.         throw("createDatasource:  unknown synctype (" + type +")");
  55.  
  56.     return model;
  57. }   
  58.  
  59. function loadDatasourceSet(allItems) {
  60.     var result = [];
  61.     var type;
  62.     var obj = {};
  63.     for(type in SYNCSET){
  64.         if(obj[type] === undefined){
  65.             if(gSettings.isSyncEnabled(type) || allItems){
  66.                 var model = createDatasource(type);
  67.                 result.push(model);
  68.             }
  69.         }
  70.     }
  71.  
  72.     
  73.     return result;
  74. }
  75.  
  76. function SyncServer() {
  77. }
  78.  
  79. SyncServer.prototype = {
  80.     _syncEngine: null,
  81.     _request: null,
  82.     _baselineCache: {},
  83.     cancel: function() {
  84.         if(this._syncEngine) {
  85.             this._syncEngine.cancel();
  86.         } 
  87.         if (this._request){
  88.             this._request.Cancel();
  89.         }
  90.     },
  91.     datacancel: function(){
  92.         if (this._request){
  93.             this._request.Cancel();
  94.         }
  95.     },
  96.     _createSyncEngine: function(datasource){
  97.         var cls = gSettings.useOwnServer ?
  98.             OwnSyncEngine : 
  99.             Syncd2SyncEngine;
  100.         var result = new cls(this,datasource);
  101.  
  102.         result.manual = this.manual;
  103.         return result;
  104.     },
  105.  
  106.     getBaseline: function(syncType,callback){
  107.         var ds = createDatasource(syncType);
  108.         var se = this._createSyncEngine(ds);
  109.         se._fetchBaseline(function(status){
  110.             if(se._responseOK(status, callback)){
  111.                 callback(0, se._baseline);
  112.             }
  113.         });
  114.     },
  115.     suspendWatcher: function(state){
  116.         var os = Cc["@mozilla.org/observer-service;1"]
  117.             .getService(Ci.nsIObserverService);
  118.         os.notifyObservers(null, "foxmarks-watchersuspend", 
  119.             state ? "1" : "0");
  120.     },
  121.  
  122.  
  123.     _doPerDatasource: function(funcname, callback, arg2){
  124.         var list = loadDatasourceSet();
  125.         var that = this;
  126.         var ds = {};
  127.  
  128.         try {
  129.             that.suspendWatcher(true);
  130.         
  131.             // loop through each set of syncdata, using the 
  132.             //  chained callback mechanism, dropping out if
  133.             //  we encounter an error
  134.             var mycallback = function(statuscode){
  135.                 if(statuscode == 0 || statuscode == 444){
  136.                     // 444 -handle purged state
  137.                     if(statuscode == 444){
  138.                         // if we allow purge from other data types
  139.                         // we'll need to modify this string
  140.                         FoxmarksAlert(Bundle().GetStringFromName(
  141.                             "msg.passwordsyncpurged"));
  142.                         gSettings.setSyncEnabled(ds.syncType, false);
  143.                         statuscode = 0;
  144.                     }
  145.                     if(ds && ds.syncType !== undefined){
  146.                         gSettings.SyncComplete(ds.syncType);
  147.                         SetProgressComponentStatus(ds.syncType, "end");
  148.                     }
  149.                     ds = list.shift();
  150.                     if(ds){
  151.                         that._syncEngine = that._createSyncEngine(ds);
  152.                         SetProgressComponentStatus(ds.syncType, "start");
  153.                         if(arg2 !== undefined){
  154.                             that._syncEngine[funcname](arg2, mycallback);
  155.                         }
  156.                         else {
  157.                             that._syncEngine[funcname](mycallback);
  158.                         }
  159.                     }
  160.                     else {
  161.                         callback(statuscode);
  162.                         that.suspendWatcher(false);
  163.                         that._syncEngine = null;
  164.                         if(!gSettings.useBaselineCache){
  165.                             that._baselineCache = {};
  166.                         }
  167.                     }
  168.                 }
  169.                 else {
  170.                     callback(statuscode);
  171.                     that.suspendWatcher(false);
  172.                     that._syncEngine = null;
  173.                     if(!gSettings.useBaselineCache){
  174.                         that._baselineCache = {};
  175.                     }
  176.                 }
  177.             }
  178.  
  179.             mycallback(0);
  180.         }catch(e){
  181.             that.suspendWatcher(false);
  182.             if(typeof e == "object" && e.message){
  183.                 LogWrite("Sync Error: " + e.message + "(" +
  184.                         e.fileName + ": " + e.lineNumber + ")");
  185.             } else {
  186.                 LogWrite("Synchronization failed. Error is " + 
  187.                     e.toSource() + " (type = " + typeof e + ")");
  188.             }
  189.             if(typeof e == "number"){
  190.                 mycallback(e);
  191.             } else {
  192.                 mycallback(4);
  193.             }
  194.         }
  195.     },
  196.     sync: function(prevState, callback){
  197.         SetProgressMessage("progress.syncing");
  198.         this._doPerDatasource("sync", callback, prevState);
  199.     },
  200.     upload: function(callback){
  201.         SetProgressMessage("progress.syncing");
  202.         this._doPerDatasource("upload", callback);
  203.     },
  204.     download: function(callback){
  205.         SetProgressMessage("progress.syncing");
  206.         this._doPerDatasource("download", callback);
  207.     },
  208.     merge: function(local, callback){
  209.         SetProgressMessage("progress.syncing");
  210.         this._doPerDatasource("merge", callback, local);
  211.     },
  212.     status: function(syncType, callback){
  213.         var ds = createDatasource(syncType);
  214.         var se = this._createSyncEngine(ds);
  215.         se.status(callback);
  216.     },
  217.     extstatus: function(syncType, callback){
  218.         var ds = createDatasource(syncType);
  219.         var se = this._createSyncEngine(ds);
  220.         se.extstatus(callback);
  221.     },
  222.     getProfileNames: function(callback){
  223.         var se = this._createSyncEngine(null);
  224.         se.getProfileNames(callback);
  225.     },
  226.     _getCorrelatorInfo: function(url, corruri, callback){
  227.         const https = "https://";
  228.         var str;
  229.         var self = this;
  230.         var protocol = url.substr(0,https.length).toLowerCase();
  231.         var funcFinished = function(status, response) {
  232.             self._request = null;
  233.             callback(status, response);
  234.         };
  235.  
  236.         // we contact correlator via https if the url we are interested
  237.         // in is https
  238.         if (gSettings.securityLevel == 1 || protocol == https ){ 
  239.             str = https;
  240.         } else {
  241.             str = "http://";
  242.         }
  243.  
  244.         str += gSettings.apiHost + corruri;
  245.         this._request = new Request(
  246.             "POST",
  247.             str,
  248.             {
  249.                 urls: [url],
  250.                 mid: gSettings.machineId,
  251.                 cid: "xmfx"
  252.             }, false, null, false, true);
  253.         this._request.Start(funcFinished);
  254.     },
  255.     getSimilarSites: function(url, callback){
  256.         return this._getCorrelatorInfo(url, "/internal/related/read", callback);
  257.     },
  258.     getTurboTags: function(url, callback){
  259.         return this._getCorrelatorInfo(url, "/internal/topics/read", callback);
  260.     },
  261.     purgepasswords: function(callback){
  262.         var ds = createDatasource("passwords");
  263.         var se = this._createSyncEngine(ds);
  264.         se.purgepasswords(callback);
  265.     },
  266.     verifypin: function(pin, callback){
  267.         var ds = createDatasource("passwords");
  268.         var se = this._createSyncEngine(ds);
  269.         try {
  270.             se.verifypin(pin, callback);
  271.         } catch(e){
  272.             LogWrite("Verify Pin Failed (" + e.message + ")");
  273.             if(typeof e == "number"){
  274.                 callback(e);
  275.             } else {
  276.                 callback(4);
  277.             }
  278.         }
  279.     },
  280.     runUnitTest: function(){
  281.         gFoxmarksUT.run();
  282.     },
  283.     countItems: function(synctype, itemtype, callback){
  284.         var ds = createDatasource(synctype);
  285.         var se = this._createSyncEngine(ds);
  286.         se.countItems(itemtype, callback);
  287.     }
  288. };
  289.  
  290.  
  291. function SyncEngine(datasource) {
  292.     this._datasource = datasource;
  293. }
  294.  
  295. SyncEngine.prototype = {
  296.     request: null,
  297.     _iscancelled: false,
  298.  
  299.     get _baseline() {
  300.         return this._mgr._baselineCache[
  301.             this._datasource.syncType
  302.         ];
  303.     },
  304.     set _baseline(val) {
  305.         this._mgr._baselineCache[
  306.             this._datasource.syncType
  307.         ] = val;
  308.     },
  309.  
  310.     cancel:  function() {
  311.         if (this.request) {
  312.             this.request.Cancel();
  313.         }
  314.         this._iscancelled = true;
  315.     },
  316.     isCancelled: function(){
  317.         return this._iscancelled;
  318.     },
  319.  
  320.     _responseOK: function(response, callback) {
  321.         if(this.isCancelled()){
  322.             callback(2);
  323.             return false;
  324.         }
  325.         else if (typeof response == 'number') {
  326.             if (!response) {
  327.                 return true;
  328.             } else {
  329.                 callback(response);
  330.             }
  331.         } else if (typeof response == 'object') {
  332.             if (!response.status) {
  333.                 return true;
  334.             } else {
  335.                 if (response.status == 403) {
  336.                     LogWrite("Got a 403");
  337.                     Handle403(response);
  338.                 } else if (response.status == 503) {
  339.                     try {
  340.                         gServerBackoff[this._datasource.syncType] = response.backoff_delay || 0;
  341.                         if (gServerBackoff[this._datasource.syncType]) {
  342.                             LogWrite("Server wants us to wait " +
  343.                                 gServerBackoff[this._datasource.syncType] + " seconds");
  344.                         }
  345.                     } catch(e) {}
  346.                 }
  347.                 callback(response.status);
  348.                 return false;
  349.             }
  350.         } else {
  351.             throw Error("unexpected response type: " + response);
  352.         }
  353.     },
  354.     countItems: function(itemtype, callback){
  355.         var self = this;
  356.         var ns = new Nodeset(self._datasource);
  357.         ns.FetchFromNative(function(status){
  358.             if (self._responseOK(status, callback)) {
  359.                 var ctr = 0;
  360.                 ns.OnTree(
  361.                     function(nid){
  362.                       if(ns.Node(nid).ntype == itemtype)
  363.                         ctr++;
  364.                     },
  365.                     function(){
  366.                         callback(status, ctr);
  367.                     }
  368.                 );
  369.             }
  370.         });
  371.     },
  372.     sync: function(prevState, callback) {
  373.         // Local Vars
  374.         var lns = null, sns = null;
  375.         var lcs = null, scs = null;
  376.         var sco = null;
  377.         var lastModified = 0;
  378.         var self = this;
  379.         var fms = Cc["@foxcloud.com/extensions/foxmarks;1"].
  380.             getService(Ci.nsIFoxmarksService);
  381.         var funcCheckStatus = function(status, response) {
  382.             if (self._responseOK(status, callback)) {
  383.                 if (response.isreset == true) {
  384.                     self.upload(callback);
  385.                 } else {
  386.                     self.merge(true, callback);
  387.                 }
  388.             }
  389.         };
  390.  
  391.         // Local functions
  392.         var funcGetServerChanges = function(status) {
  393.             if (self._responseOK(status, callback)) {
  394.                 // Get changes.
  395.                 self._getServerChanges(funcGotServerChanges);
  396.             }
  397.         };
  398.         var funcGotServerChanges = function(status, serverContextObject) {
  399.             if (self._responseOK(status, callback)) {
  400.                 sco = serverContextObject;
  401.                 if (!sco.continuous) {
  402.                     // Someone has done an upload. Allow the user the option
  403.                     // of performing a merge or a download.
  404.  
  405.                     switch (self._datasource.DiscontinuityPrompt()) {
  406.                     case 0: // merge
  407.                         return self.merge(false, callback);
  408.  
  409.                     case 1: // download
  410.                         return self.download(callback);
  411.  
  412.                     case 2: // cancel
  413.                         callback(2);
  414.                         return;
  415.                     }
  416.                 }
  417.                 if (!sco.mscs.length && prevState == 'ready' && 
  418.                         self._haveFetched) {
  419.                     LogWrite("Nothing to see here; move along.");
  420.                     callback(0);
  421.                     return;
  422.                 }
  423.  
  424.                 //SetProgressMessage("progress.syncing");
  425.                 scs = sco.mscs;
  426.                 sns = sco.sns;
  427.  
  428.                 // Calculate minimal local change set.
  429.                 lastModified = fms.getLastModified(self._datasource.synctype);
  430.                 lns = new Nodeset(self._datasource);
  431.                 lns.FetchFromNative(funcGotLocalNodeset);
  432.                 self._haveFetched = true;
  433.             }
  434.         }
  435.         var funcGotLocalNodeset = function(status) {
  436.             if (self._responseOK(status, callback)) {
  437.                 var base = new Nodeset(self._datasource,self._baseline);
  438.                 base.Compare(lns, funcGetLocalCommandset);
  439.             }
  440.         }
  441.         var funcGetLocalCommandset = function(status, lcs) {
  442.             if (self._responseOK(status, callback)) {
  443.                 // Check to see whether there's been a clobber
  444.                 if (lns.length < 3 * self._baseline.length / 4) {
  445.                     LogWrite("Yikes! Length was " + self._baseline.length + 
  446.                         " but is now " + lns.length);
  447.                     self.manual = true;
  448.                     if (!self._datasource.ClobberDialog(lns.length, self._baseline.length)) {
  449.                         // User canceled. Back off for 24 hours.
  450.                         gBackoffUntil = Date.now() + 24 * 60 * 60 * 1000;
  451.                         callback(2);
  452.                         return;
  453.                     }
  454.                 }
  455.             
  456.                 LogWrite("lcs = " + lcs.length + " scs = " + scs.length);
  457.  
  458.                 try {
  459.                     Synchronize(self._baseline, lcs, scs, lns, sns, 
  460.                         funcSyncComplete);
  461.                 } catch (e) {
  462.                     LogWrite("Synchronization failed. Error is " + 
  463.                         e.toSource());
  464.                     if(typeof e == "number"){
  465.                         callback(e);
  466.                     } else {
  467.                         callback(4);
  468.                     }
  469.                 }
  470.             }
  471.         };
  472.         var funcSyncComplete = function(finalLcs,
  473.                 finalScs,
  474.                 conflicts,
  475.                 showedUI) {
  476.             //SetProgressMessage("progress.writing");
  477.  
  478.             if (showedUI) {
  479.                 self.manual = true;
  480.             }
  481.  
  482.             if (lastModified != fms.getLastModified(self._datasource.synctype)) {
  483.                 LogWrite("Local datastore changed during sync");
  484.                 LogWrite("lastModified " + lastModified + " != " + fms.getLastModified(self._datasource.synctype));
  485.                 callback(409);
  486.                 return;
  487.             }
  488.  
  489.             // Scoping: assign back to our enclosing function's locals
  490.             lcs = finalLcs;
  491.             scs = finalScs;
  492.  
  493.             // Apply the server's changes to the local set.
  494.             try {
  495.                 lns = new Nodeset(self._datasource, lns);
  496.                 scs.execute(lns);
  497.             } catch (e) {
  498.                 LogWrite("execute failed: " + e.toSource());
  499.                 if(typeof e == "number"){
  500.                     callback(e);
  501.                 } else {
  502.                     callback(4);
  503.                 }
  504.                 return;
  505.             }
  506.             
  507.             // Write local changes (if any) to the server.
  508.             if (lcs.set.length > 0) {
  509.                 self._writeServerChanges(lcs, lns, sco, conflicts, 
  510.                     funcWroteServerChanges);
  511.             } else {
  512.                 funcWroteServerChanges();
  513.             }
  514.         };
  515.         var funcWroteServerChanges = function(obj) {
  516.             if (obj) {      // We had something to write
  517.                 if (self._responseOK(obj, callback)) {
  518.                 } else {
  519.                     LogWrite("putchanges failed; response is " + 
  520.                             obj.toJSONString());
  521.                     return;
  522.                 }
  523.             }
  524.  
  525.             // Flush the local nodeset back to the native datastore if it changed.
  526.             if (scs.set.length > 0) {
  527.                 //SetProgressMessage("progress.loading");
  528.                 lns.FlushToNative(funcWroteLocalChanges);
  529.             } else {
  530.                 funcWroteLocalChanges(0);
  531.             }
  532.         };
  533.         var funcWroteLocalChanges = function(response) {
  534.             // If we got changes from the server or wrote changes to
  535.             // the server, we'll have a new revision number. If neither
  536.             // of these happened, then the sync was a big no-op.
  537.             if (self._responseOK(response, callback)) {
  538.                 if (lcs.set.length > 0 || scs.set.length > 0) {
  539.                     self._completeTransaction(lns, sco, callback);
  540.                 } else {
  541.                     // We didn't do anything, but note that we successfully
  542.                     // Synced nonetheless.
  543.                     callback(0);         // Done!
  544.                 }
  545.             }
  546.         };
  547.  
  548.         // function start
  549.  
  550.         // If we've never synced, do an upload or merge.
  551.         if (!gSettings.getHaveSynced(self._datasource.syncType)) {
  552.             self.status(funcCheckStatus);
  553.         }
  554.         else {
  555.             if(gSettings.mustUpload(self._datasource.syncType)){
  556.                 LogWrite("Forced Upload: Pin Reset");
  557.                 self.upload(function(response){
  558.                     if(self._responseOK(response, callback)){
  559.                         gSettings.setMustUpload(self._datasource.syncType, false);
  560.                         callback(response);
  561.                     }
  562.                 });
  563.                 return;
  564.  
  565.             }
  566.             else if(gSettings.mustMerge(self._datasource.syncType)){
  567.                 LogWrite("Forced Merge for Passwords");
  568.                 self.merge(true, function(response){
  569.                     if(self._responseOK(response, callback)){
  570.                         gSettings.setMustMerge(self._datasource.syncType, false);
  571.                         callback(response);
  572.                     }
  573.                 });
  574.                 return;
  575.  
  576.             }
  577.             // Normal sync: make sure we've got a baseline to work with.
  578.             //SetProgressMessage("progress.downloading");
  579.             self._fetchBaseline(funcGetServerChanges);
  580.             return;
  581.         }
  582.     }
  583. };
  584.  
  585.  
  586. function Syncd2SyncEngine(mgr, datasource) {
  587.     this._datasource = datasource;
  588.     this._mgr = mgr;
  589. }
  590. Syncd2SyncEngine.prototype = new SyncEngine;
  591.  
  592. function OwnSyncEngine(mgr, datasource) {
  593.     this._datasource = datasource;
  594.     this._mgr = mgr;
  595. }
  596. OwnSyncEngine.prototype = new SyncEngine;
  597.  
  598. Syncd2SyncEngine.prototype._args = function(dict, ignoreUpload) {
  599.     if (this.manual) {
  600.         dict["manual"] = true;
  601.     }
  602.     if (gFailureCount) {
  603.         dict["retry"] = gFailureCount;
  604.     }
  605.     if (gSettings.machineId) {
  606.         if (!dict["log"]) {
  607.             dict["log"] = {};
  608.         }
  609.         dict["log"]["mid"] = gSettings.machineId;
  610.         dict["log"]["serp"] = gSettings.serpEnabled ? gSettings.serpMaxItems : 0;
  611.         dict["log"]["ssEnabled"] = gSettings.simsiteEnabled;
  612.  
  613.         var st = [];
  614.         for(var x = 0; x < 10; x++){
  615.             st.push(gSettings.getST(false,x));
  616.         }
  617.  
  618.         if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
  619.             dict["log"]["st"] = st;
  620.         }
  621.         st = [];
  622.         for(var x = 0; x < 10; x++){
  623.             st.push(gSettings.getST(true,x));
  624.             gSettings.clearST(x);
  625.         }
  626.  
  627.         if(st.toSource() != "[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"){
  628.             dict["log"]["ust"] = st;
  629.         }
  630.         if(gSettings.trSERP){
  631.             dict["log"]["trs"] = gSettings.trSERP;
  632.             gSettings.trSERP = "";
  633.         }
  634.         if(gSettings.numTurboTags){
  635.             dict["log"]["ttags"] = gSettings.numTurboTags;
  636.             gSettings.numTurboTags = 0;;
  637.         }
  638.     }
  639.     if (gSettings.viewId) {
  640.         if(!ignoreUpload){
  641.             dict.view = gSettings.viewId;
  642.         }
  643.     }
  644.  
  645.  
  646.     return dict;
  647. }
  648.  
  649. Syncd2SyncEngine.prototype.upload = function(callback) {
  650.     var self = this;
  651.     var ns = new Nodeset(this._datasource);
  652.  
  653.     // if we are doing a force upload, then that trumps forcedMerge
  654.     if(gSettings.mustUpload(self._datasource.syncType)){
  655.         gSettings.setMustMerge(self._datasource.syncType, false);
  656.     }
  657.     // even though the user says upload, we don't want
  658.     // to do that for passwords during initial sync
  659.     if(gSettings.mustMerge(self._datasource.syncType)){
  660.         LogWrite("Forced Merge for Passwords");
  661.         self.merge(true, function(response){
  662.             if(self._responseOK(response, callback)){
  663.                 gSettings.setMustMerge(self._datasource.syncType, false);
  664.                 callback(response);
  665.             }
  666.         });
  667.         return;
  668.     }
  669.  
  670.     // Hack: we don't seem to be able to handle
  671.     // authentication during an upload request.
  672.     // So to avoid trouble, we do a throw-away
  673.     // status request first. If auth is required,
  674.     // it will be handled during the status request,
  675.     // after which we will get on to our real work.
  676.  
  677.     var funcForceAuth = function(status) {
  678.         if (self._responseOK(status, callback)) {
  679.             //SetProgressMessage("progress.writing");
  680.             ns.FetchFromNative(funcFetched);
  681.         }
  682.     };
  683.     var funcFetched = function(e) {
  684.         if (e) {
  685.             callback(e);
  686.             return;
  687.         }
  688.         ns.ProvideCommandset(funcContinue);
  689.     };
  690.     var funcContinue = function(status, cs) {
  691.         if (self._responseOK(status, callback)) {
  692.             self.request = new Request("POST", 
  693.                 {
  694.                     path: "/sync/" + self._datasource.syncType + "/upload",
  695.                     host: gSettings.getServerHost(self._datasource.syncType)
  696.                 }, 
  697.                 self._args({ "commands" : cs },
  698.                     gSettings.mustUpload(self._datasource.syncType)
  699.                 )
  700.             );
  701.             self.request.Start(funcDone);
  702.         }
  703.     };
  704.     var funcDone = function(response) {
  705.         if (self._responseOK(response, callback)) {
  706.             if (!response.toprev) {
  707.                 LogWrite("Error: Invalid response to upload");
  708.                 callback(500);
  709.             } else {
  710.                 gSettings.setMustUpload(self._datasource.syncType, false);
  711.                 self._completeTransaction(ns, { toprev: response.toprev }, 
  712.                     callback);
  713.             }
  714.         }
  715.     };
  716.  
  717.     if (gSettings.viewId && !gSettings.mustUpload(self._datasource.syncType)) {
  718.         this._uploadWithProfile(callback);
  719.         return;
  720.     }
  721.     self.status(funcForceAuth);
  722. }
  723.  
  724. Syncd2SyncEngine.prototype.purgepasswords = function(callback) {
  725.     var self = this;
  726.     var ns = new Nodeset(this._datasource);
  727.  
  728.     // Hack: we don't seem to be able to handle
  729.     // authentication during an upload request.
  730.     // So to avoid trouble, we do a throw-away
  731.     // status request first. If auth is required,
  732.     // it will be handled during the status request,
  733.     // after which we will get on to our real work.
  734.  
  735.     var funcForceAuth = function(status) {
  736.         if (self._responseOK(status, callback)) {
  737.             self.request = new Request("POST", 
  738.                 {
  739.                     path: "/sync/" + self._datasource.syncType + "/purge",
  740.                     host: gSettings.getServerHost(self._datasource.syncType)
  741.                 } , self._args({ mode: "data"}));
  742.             self.request.Start(function(statuspurge){
  743.                 if (self._responseOK(statuspurge, callback)) {
  744.                     gSettings.SetSyncRevision(
  745.                         self._datasource.syncType, 0);
  746.                     callback(0);
  747.                 }
  748.            });
  749.         }
  750.     };
  751.     self.status(funcForceAuth);
  752. }
  753. Syncd2SyncEngine.prototype._uploadWithProfile = function(callback) {
  754.     var self = this;
  755.     var revision;
  756.     var ns;
  757.     var lns;
  758.  
  759.     var funcDone = function(response) {
  760.         if (self._responseOK(response, callback)) {
  761.             if (!response.toprev || !response.commands) {
  762.                 LogWrite("Error: Invalid response to download");
  763.                 callback(500);
  764.                 return;
  765.             }
  766.             revision = response.toprev;
  767.             ns  = new Nodeset(self._datasource);
  768.             var cs = new Commandset(response.commands);
  769.             try {
  770.                 cs.execute(ns);
  771.             } catch (e) {
  772.                 LogWrite("execute failed: " + e.toSource());
  773.                 if(typeof e == "number"){
  774.                     callback(e);
  775.                 } else {
  776.                     callback(4);
  777.                 }
  778.                 return;
  779.             }
  780.  
  781.             // Retrieve local set.
  782.             lns = new Nodeset(self._datasource);
  783.             lns.FetchFromNative(funcGotLocal);
  784.         }
  785.     };
  786.     var funcGotLocal = function(response) {
  787.         // Calculate difference: server -> local.
  788.         if (self._responseOK(response, callback)) {
  789.             ns.Compare(lns, funcCompared);
  790.         }
  791.     };
  792.     var funcCompared = function(status, cs) {
  793.         if (self._responseOK(status, callback)) {
  794.             if (cs.length) {
  795.                 //SetProgressMessage("progress.uploading");
  796.                 self.request = new Request("POST",
  797.                     {
  798.                         path: "/sync/" + self._datasource.syncType + "/putchanges",
  799.                         host: gSettings.getServerHost(self._datasource.syncType)
  800.                     }, 
  801.                     self._args({ "baserev": revision, "commands" : cs }));
  802.                 self.request.Start(funcFinished);
  803.             } else {
  804.                 funcFinished({ status: 0, toprev: revision});
  805.             }
  806.         }
  807.     };
  808.     var funcFinished = function(response) {
  809.         if (self._responseOK(response, callback)) {
  810.             if (!response.toprev) {
  811.                 LogWrite("Error: Invalid response to putchanges");
  812.                 callback(500);
  813.             } else {
  814.                 self._completeTransaction(ns, { toprev: response.toprev }, 
  815.                     callback);
  816.             }
  817.         }
  818.     };
  819.  
  820.  
  821.     // Download current server state.
  822.     SetProgressMessage("progress.downloading");
  823.     self.request = new Request("POST",
  824.         {
  825.             path: "/sync/"  + self._datasource.syncType + "/download",
  826.             host: gSettings.getServerHost(self._datasource.syncType)
  827.         }, self._args({}));
  828.     self.request.Start(funcDone);
  829. }
  830.  
  831. Syncd2SyncEngine.prototype.download = function(callback) {
  832.     var self = this;
  833.     var ns;
  834.     var revision;
  835.  
  836.     // resetPIN trumps download (only occurs in the case of
  837.     // resetPIN in setup dialog)
  838.     if(gSettings.mustUpload(self._datasource.syncType)){
  839.         LogWrite("Forced Upload: Pin Reset");
  840.         self.upload(function(response){
  841.             if(self._responseOK(response, callback)){
  842.                 gSettings.setMustUpload(self._datasource.syncType, false);
  843.                 callback(response);
  844.             }
  845.         });
  846.         return;
  847.     }
  848.     else if(gSettings.mustMerge(self._datasource.syncType)){
  849.         LogWrite("Forced Merge for Passwords");
  850.         self.merge(true, function(response){
  851.             if(self._responseOK(response, callback)){
  852.                 gSettings.setMustMerge(self._datasource.syncType, false);
  853.                 callback(response);
  854.             }
  855.         });
  856.         return;
  857.     }
  858.     var funcDone = function(response) {
  859.         if (self._responseOK(response, callback)) {
  860.             if (!response.toprev || !response.commands) {
  861.                 LogWrite("Error: Invalid response to download");
  862.                 callback(500);
  863.                 return;
  864.             }
  865.             revision = response.toprev;
  866.             ns = new Nodeset(self._datasource);
  867.             var cs = new Commandset(response.commands);
  868.             try {
  869.                 cs.execute(ns);
  870.             } catch (e) {
  871.                 LogWrite("execute failed: " + e.toSource());
  872.                 if(typeof e == "number"){
  873.                     callback(e);
  874.                 } else {
  875.                     callback(4);
  876.                 }
  877.                 return;
  878.             }
  879.             //SetProgressMessage("progress.loading");
  880.             ns.FlushToNative(funcFinished);
  881.         }
  882.     };
  883.     var funcFinished = function(response) {
  884.         if (self._responseOK(response, callback)) {
  885.             self._completeTransaction(ns, { toprev: revision }, callback);
  886.         }
  887.     };
  888.  
  889.     //SetProgressMessage("progress.downloading");
  890.     self.request = new Request("POST", 
  891.         {
  892.             path: "/sync/" + self._datasource.syncType + "/download",
  893.             host: gSettings.getServerHost(self._datasource.syncType)
  894.         }, self._args({}));
  895.     self.request.Start(funcDone);
  896. }
  897.  
  898.  
  899. Syncd2SyncEngine.prototype.verifypinbrokenroot = function(pin, callback) {
  900.     var self = this;
  901.     var ns;
  902.     var revision;
  903.  
  904.     var funcDone = function(response) {
  905.         if (self._responseOK(response, callback)) {
  906.             for(var nid in response.nodes){
  907.                 if(!response.nodes.hasOwnProperty(nid)){
  908.                     continue;
  909.                 } else if(response.nodes[nid].data){
  910.                     var result = self._datasource.verifyPin(pin, response.nodes[nid]); 
  911.                     callback(result ? 0 : 100);
  912.                     return;
  913.                 }
  914.             }
  915.             // must have been no nodes with data, so assume it's valide
  916.             callback(0);
  917.         }
  918.     };
  919.  
  920.     //SetProgressMessage("progress.verifying");
  921.     LogWrite("Verifying PIN Broken Root");
  922.     self.request = new Request("POST", 
  923.         {
  924.             path: "/sync/" + self._datasource.syncType + "/state",
  925.             host: gSettings.getServerHost(self._datasource.syncType)
  926.         }, {"nodes":"ROOT", "depth":"children"});
  927.     self.request.Start(funcDone);
  928. }
  929.  
  930. Syncd2SyncEngine.prototype.verifypin = function(pin, callback) {
  931.     var self = this;
  932.     var ns;
  933.     var revision;
  934.  
  935.     var funcDone = function(response) {
  936.         if (self._responseOK(response, callback)) {
  937.            LogWrite("Verifying PIN (Received Test Node)");
  938.            if(!response.nodes.ROOT.data){
  939.                 LogWrite("WARNING: Bad Root Problem");
  940.                 self.verifypinbrokenroot(pin, callback);
  941.            } else {
  942.                 var result = self._datasource.verifyPin(pin, response.nodes.ROOT); 
  943.                 callback(result ? 0 : 100);
  944.            }
  945.         }
  946.     };
  947.  
  948.     //SetProgressMessage("progress.verifying");
  949.     LogWrite("Verifying PIN (Starting)");
  950.     self.request = new Request("POST", 
  951.         {
  952.             path: "/sync/" + self._datasource.syncType + "/state",
  953.             host: gSettings.getServerHost(self._datasource.syncType)
  954.         }, {"nodes":"ROOT", "depth":"self"});
  955.     self.request.Start(funcDone);
  956. }
  957. // Fetch changes from the server. Passes to callback an object containing:
  958. // * continous: true if the changes on the server are continuous.
  959. // * mscs: the minimal server commandset. 
  960. // * other server context info (in this case, toprev) to passed into
  961. //   _writeServerChanges().
  962.  
  963.  
  964. Syncd2SyncEngine.prototype._getServerChanges = function(callback) {
  965.     // Get changes.
  966.     var self = this;
  967.     var toprev = 0;
  968.     var sns = new Nodeset(self._datasource, self._baseline);
  969.  
  970.     var funcProcessResponse = function(response) {
  971.         if (self._responseOK(response, callback)) {
  972.             if (response.continuous == false) {
  973.                 callback(0, { continuous: false });
  974.             } else {
  975.                 if (!response.toprev || !response.commands) {
  976.                     LogWrite("Error: Invalid response to getchanges");
  977.                     callback(500);
  978.                     return;
  979.                 }
  980.                 toprev = response.toprev;
  981.                 // Calculate mcs.
  982.                 if (response.commands.length) {
  983.                     scs = new Commandset(response.commands);
  984.                     try {
  985.                         scs.execute(sns);
  986.                     } catch (e) {
  987.                         LogWrite("execute failed: " + e.toSource());
  988.                         if(typeof e == "number"){
  989.                             callback(e);
  990.                         } else {
  991.                             callback(4);
  992.                         }
  993.                         return;
  994.                     }
  995.                     var base = new Nodeset(self._datasource,self._baseline);
  996.                     base.Compare(sns, funcCalculatedMcs);
  997.                 } else {
  998.                     // Not modified
  999.                     callback(0, { mscs: new Commandset(), toprev: toprev, 
  1000.                             sns: sns, continuous: true});
  1001.                 }
  1002.             }
  1003.         }
  1004.     };
  1005.     var funcCalculatedMcs = function(status, cs) {
  1006.         if (self._responseOK(status, callback)) {
  1007.             LogWrite(">>> Finished mcs: " + cs.toSource());
  1008.             callback(0, { mscs: cs, toprev: toprev, continuous: true, 
  1009.                     sns: sns });
  1010.         }
  1011.     };
  1012.  
  1013.     self.request = new Request("POST", 
  1014.         {
  1015.             path: "/sync/" + self._datasource.syncType + "/getchanges",
  1016.             host: gSettings.getServerHost(self._datasource.syncType)
  1017.         },
  1018.         self._args({ "baserev": self._baseline.currentRevision}) );
  1019.     self.request.Start(funcProcessResponse);
  1020. }
  1021.  
  1022. OwnSyncEngine.prototype.testDupURL = function(){
  1023.     // force check to see if user has two different urls for password
  1024.     // and bookmark
  1025.     if(gSettings.isSyncEnabled("bookmarks") && 
  1026.         gSettings.isSyncEnabled("passwords")){
  1027.         if(gSettings.getUrlWithUsernameAndPassword("passwords") ==
  1028.             gSettings.getUrlWithUsernameAndPassword("bookmarks")){
  1029.                 return true;
  1030.         }
  1031.     }
  1032.     return false;
  1033. }
  1034. OwnSyncEngine.prototype._getServerChanges = function(callback) {
  1035.     // Download the file, but only if the etag doesn't match
  1036.     var self = this;
  1037.     var headers = {};
  1038.     var token;
  1039.     var etag;
  1040.     var sns;
  1041.  
  1042.     var funcProcessFile = function(response) {
  1043.         if (response.status == 304) {   // Not modified.
  1044.             callback(0, { continuous: true, mscs: new Commandset(), 
  1045.                 token: gSettings.getToken(self._datasource.syncType), etag: response.etag });
  1046.             return;
  1047.         }
  1048.         if (self._responseOK(response, callback)) {
  1049.             if (response.token != gSettings.getToken(self._datasource.syncType)) {
  1050.                 callback(0, { continuous: false, etag: response.etag } );
  1051.                 return;
  1052.             }
  1053.  
  1054.             token = response.token;
  1055.             etag = response.etag;
  1056.             var cs = new Commandset(response.commands);
  1057.             sns = new Nodeset(self._datasource);
  1058.             try {
  1059.                 sns.Execute(cs);
  1060.             } catch (e) {
  1061.                 LogWrite("execute failed: " + e.toSource());
  1062.                 if(typeof e == "number"){
  1063.                     callback(e);
  1064.                 } else {
  1065.                     callback(4);
  1066.                 }
  1067.                 return;
  1068.             }
  1069.  
  1070.             var base = new Nodeset(self._datasource,self._baseline);
  1071.             base.Compare(sns, funcCompared);
  1072.         }
  1073.     };
  1074.     var funcCompared = function(status, cs) {
  1075.         if (self._responseOK(status)) {
  1076.             callback(0, { continuous: true, mscs: cs, token: token, 
  1077.                     sns: sns, etag: etag });
  1078.         }
  1079.     };
  1080.  
  1081.     var serveretag = gSettings.getEtag(this._datasource.syncType);
  1082.     if (serveretag.length) {
  1083.         headers["If-None-Match"] = serveretag;
  1084.     }
  1085.     self.request = new Request("GET", 
  1086.         gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
  1087.         null, false, headers);
  1088.     self.request.Start(funcProcessFile);
  1089. }
  1090.  
  1091. Syncd2SyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
  1092.         callback) {
  1093.  
  1094.     var self = this;
  1095.     var funcWrote = function(response) {
  1096.         if (self._responseOK(response, callback)) {
  1097.             sco.toprev = response.toprev;
  1098.             if (!sco.toprev) {
  1099.                 LogWrite("Error: Invalid response to putchanges");
  1100.                 callback(500);
  1101.             } else {
  1102.                 callback(0);
  1103.             }
  1104.         }
  1105.     };
  1106.  
  1107.     self.request = new Request("POST", 
  1108.         {
  1109.             path: "/sync/" + self._datasource.syncType + "/putchanges",
  1110.             host: gSettings.getServerHost(self._datasource.syncType)
  1111.         },
  1112.         self._args({ baserev : sco.toprev, commands : lcs, log : conflicts } ));
  1113.     self.request.Start(funcWrote);
  1114. }
  1115.  
  1116. OwnSyncEngine.prototype._writeServerChanges = function(lcs, lns, sco, conflicts,
  1117.         callback) {
  1118.     var self = this;
  1119.  
  1120.     var funcGotCommandset = function(status, cs) {
  1121.         var header = {};
  1122.         if (!gSettings.disableIfMatchOnPut && sco.etag && sco.etag.length) {
  1123.             header = { "Etag" : sco.etag };
  1124.         }
  1125.         if (self._responseOK(status, callback)) {
  1126.             self.request = new Request("PUT", 
  1127.                 gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
  1128.                 { token: sco.token, commands: cs },
  1129.                 false, header, true);
  1130.             self.request.Start(funcWritten);
  1131.         }
  1132.     };
  1133.     var funcWritten = function(response) {
  1134.         if (self._responseOK(response, callback)) {
  1135.             sco.etag = response.etag;
  1136.             callback(0);
  1137.         }
  1138.     };
  1139.  
  1140.     lns.ProvideCommandset(funcGotCommandset);
  1141. }
  1142.  
  1143. Syncd2SyncEngine.prototype.status = function(callback) {
  1144.     var self = this;
  1145.  
  1146.     var funcCheckStatus = function(response) {
  1147.         if (self._responseOK(response, callback)) {
  1148.             if (!response.toprev) {
  1149.                 LogWrite("Error: Invalid response to status");
  1150.                 callback(500);
  1151.                 return;
  1152.             }
  1153.             callback(0, response);
  1154.         }
  1155.     };
  1156.  
  1157.     LogWrite("Entered Status...");
  1158.     //SetProgressMessage("progress.verifying");
  1159.  
  1160.     self.request = new Request("POST",
  1161.         { path: "/sync/" + self._datasource.syncType + "/status",
  1162.           host: gSettings.getServerHost(self._datasource.syncType)
  1163.         }, {});
  1164.     self.request.Start(funcCheckStatus);
  1165. }
  1166. Syncd2SyncEngine.prototype.extstatus = function(callback) {
  1167.     var self = this;
  1168.     this.status(function(normal_status,response){
  1169.         // if we get an error, do callback right away
  1170.         if(normal_status){
  1171.             callback(normal_status, response);
  1172.  
  1173.         // if we are reset, no need to check if we are purged
  1174.         } else if(response.isreset){
  1175.             response.ispurged = false;
  1176.             callback(normal_status, response);
  1177.         // call for the root to see if we get a 444
  1178.         } else {
  1179.             self.request = new Request("POST", 
  1180.                 {
  1181.                     path: "/sync/" + self._datasource.syncType + "/state",
  1182.                     host: gSettings.getServerHost(self._datasource.syncType)
  1183.                 }, {"nodes":"ROOT", "depth":"self"});
  1184.             self.request.Start(function(stateresponse){
  1185.                 if (self._responseOK(stateresponse, function(errnum){
  1186.                     if(errnum == 444){
  1187.                         response.ispurged = true;
  1188.                         callback(0, response);
  1189.                     } else {
  1190.                         callback(errnum, response);
  1191.                     }
  1192.                 })) {
  1193.                     response.ispurged = false;
  1194.                     callback(0, response);
  1195.                 }
  1196.             });
  1197.         }
  1198.     });
  1199.  
  1200. }
  1201. OwnSyncEngine.prototype.status = function(callback) {
  1202.     var self = this;
  1203.  
  1204.     var funcHandleResponse = function(response) {
  1205.         if (response.status == 404) {
  1206.             callback(0, { status: 0, isreset: true, ispurged: false });
  1207.         } else if (self._responseOK(response, callback)) {
  1208.             callback(0, { status: 0, isreset: false, ispurged: false });
  1209.         }
  1210.     };
  1211.  
  1212.     LogWrite("Entered status...");
  1213.     if(this.testDupURL()){
  1214.         callback(1011);
  1215.         return;
  1216.     }
  1217.     //SetProgressMessage("progress.verifying");
  1218.     self.request = new Request("GET",
  1219.         gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
  1220.     self.request.Start(funcHandleResponse);
  1221. }
  1222. OwnSyncEngine.prototype.extstatus = OwnSyncEngine.prototype.status;
  1223.  
  1224.  
  1225. Syncd2SyncEngine.prototype._getServerState = function(callback) {
  1226.     var self = this;
  1227.     self.request = new Request("POST", 
  1228.         {
  1229.             path: "/sync/" + self._datasource.syncType +  "/download",
  1230.             host: gSettings.getServerHost(self._datasource.syncType)
  1231.         }, self._args({}) );
  1232.     self.request.Start(callback);
  1233. }
  1234.  
  1235. OwnSyncEngine.prototype._getServerState = function(callback) {
  1236.     var self = this;
  1237.     self.request = new Request("GET",
  1238.         gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
  1239.     self.request.Start(callback);
  1240. }
  1241.  
  1242. SyncEngine.prototype.merge = function(local, callback) {
  1243.     // Perform a merge (an "additive sync").
  1244.     // local is a boolean which determines what merge uses
  1245.     // as the starting set: true for local, false for server.
  1246.  
  1247.     LogWrite("Entered Merge...");
  1248.  
  1249.     var self = this;
  1250.     var serverNS = new Nodeset(self._datasource);
  1251.     var localNS = new Nodeset(self._datasource);
  1252.     var mergedNS = null;
  1253.     var revision;
  1254.     var sco;
  1255.  
  1256.     // resetPIN trumps merge
  1257.     if(gSettings.mustUpload(self._datasource.syncType)){
  1258.         LogWrite("Forced Upload: Pin Reset");
  1259.         self.upload(function(response){
  1260.             if(self._responseOK(response, callback)){
  1261.                 gSettings.setMustUpload(self._datasource.syncType, false);
  1262.                 callback(response);
  1263.             }
  1264.         });
  1265.         return;
  1266.     }
  1267.  
  1268.     var funcDone = function(response) {
  1269.         if (self._responseOK(response, callback)) {
  1270.             sco = response;
  1271.             var cs = new Commandset(response.commands);
  1272.             try {
  1273.                 cs.execute(serverNS);
  1274.             } catch (e) {
  1275.                 if(typeof e == "number"){
  1276.                     callback(e);
  1277.                 } else {
  1278.                     LogWrite("execute failed: " + e.toSource());
  1279.                     callback(4);
  1280.                 }
  1281.                 return;
  1282.             }
  1283.             localNS.FetchFromNative(funcFetched);
  1284.         }
  1285.     };
  1286.     var funcFetched = function( e ) {
  1287.  
  1288.         if (e) {
  1289.             callback(e);
  1290.             return;
  1291.         }
  1292.         //SetProgressMessage("progress.merging");
  1293.         if (local) {
  1294.             localNS.Merge(serverNS);
  1295.             mergedNS = localNS;
  1296.         } else {
  1297.             mergedNS = new Nodeset(self._datasource, serverNS);
  1298.             mergedNS.Merge(localNS);
  1299.         }
  1300.  
  1301.         // calculate server's MCS
  1302.         LogWrite("Finished merge; calculating mcs");
  1303.         serverNS.Compare(mergedNS, funcWriteChanges);
  1304.     };
  1305.     var funcWriteChanges = function(status, cs) {
  1306.         if (self._responseOK(status, callback)) {
  1307.             // Write changes to the server.
  1308.             //SetProgressMessage("progress.writing");
  1309.             if (cs.set.length > 0) {
  1310.                 self._writeServerChanges(cs, mergedNS, sco, null, 
  1311.                     funcWroteServerChanges);
  1312.             } else {
  1313.                 funcWroteServerChanges(0);
  1314.             }
  1315.         }
  1316.     };
  1317.     var funcWroteServerChanges = function(response) {
  1318.         // Write 'em back to native store, too
  1319.         if (self._responseOK(response, callback)) {
  1320.             //SetProgressMessage("progress.loading");
  1321.             mergedNS.FlushToNative(funcFlushed);
  1322.         }
  1323.     };
  1324.     var funcFlushed = function(response) {
  1325.         if (self._responseOK(response, callback)) {
  1326.             gSettings.setMustMerge(self._datasource.syncType, false);
  1327.             self._completeTransaction(mergedNS, sco, callback);
  1328.         }
  1329.     };
  1330.  
  1331.     //SetProgressMessage("progress.downloading");
  1332.     self._getServerState(funcDone);
  1333. }
  1334.  
  1335. Syncd2SyncEngine.prototype._fetchBaseline = function(callback) {
  1336.     // Fetch the baseline if necessary
  1337.     var self = this;
  1338.  
  1339.     var funcLoadedFromServer = function(response) {
  1340.         if (self._responseOK(response, callback)) {
  1341.             var cs = new Commandset(response.commands);
  1342.             try {
  1343.                 cs.execute(self._baseline);
  1344.             } catch (e) {
  1345.                 LogWrite("execute failed: " + e.toSource());
  1346.                 if(typeof e == "number"){
  1347.                     callback(e);
  1348.                 } else {
  1349.                     callback(4);
  1350.                 }
  1351.                 return;
  1352.             }
  1353.             self._baseline.currentRevision = response.rev;
  1354.             self._baseline.SaveToFile(funcLoaded);
  1355.             return;
  1356.         } else {
  1357.             LogWrite("Failed to load baseline from server.");
  1358.         }
  1359.     };
  1360.     var funcLoaded = function() {
  1361.         // Loaded from file or server
  1362.         // var nat = new NativeDatasource();
  1363.         self._baseline.BaselineLoaded(self._baseline, funcGotIt);
  1364.         return;
  1365.     };
  1366.     var funcGotIt = function(status) {
  1367.         if (self._responseOK(status, callback)) {
  1368.             self._baseline.hash = gSettings.hash;
  1369.             callback(0);
  1370.         }
  1371.     };
  1372.  
  1373.     if (self._baseline && self._baseline.hash == gSettings.hash) {
  1374.         LogWrite("Using Baseline Cache.");
  1375.         funcGotIt(0);
  1376.         return;
  1377.     }
  1378.  
  1379.     self._baseline = new Nodeset(self._datasource);
  1380.     try {
  1381.         self._baseline.LoadFromFile();
  1382.     } catch (e) {
  1383.         // We failed to load our baseline locally -- try getting
  1384.         // it from the server.
  1385.         LogWrite("Failed to load baseline from file: " + e.name);
  1386.         var request = new Request("POST",
  1387.             {
  1388.                 path: "/sync/" + self._datasource.syncType + "/download",
  1389.                 host: gSettings.getServerHost(self._datasource.syncType)
  1390.             },
  1391.             self._args({ rev: gSettings.GetSyncRevision(self._datasource.syncType), depth: "all", 
  1392.                     log: { "error": e.name }}));
  1393.         request.Start(funcLoadedFromServer);
  1394.         return;
  1395.     }
  1396.  
  1397.     funcLoaded();
  1398. }
  1399.  
  1400. OwnSyncEngine.prototype._fetchBaseline = function(callback) {
  1401.     // Fetch the baseline if necessary
  1402.     var self = this;
  1403.  
  1404.     var funcLoaded = function() {
  1405.         // Loaded from file 
  1406.         // var nat = new NativeDatasource();
  1407.         self._baseline.BaselineLoaded(self._baseline, funcGotIt);
  1408.     };
  1409.     var funcGotIt = function(status) {
  1410.         if (self._responseOK(status, callback)) {
  1411.             self._baseline.hash = gSettings.hash;
  1412.             callback(0);
  1413.         }
  1414.     };
  1415.  
  1416.     if (self._baseline && self._baseline.hash == gSettings.hash) {
  1417.         funcGotIt(0);
  1418.         return;
  1419.     }
  1420.  
  1421.     self._baseline = new Nodeset(self._datasource);
  1422.     try {
  1423.         self._baseline.LoadFromFile();
  1424.     } catch (e) {
  1425.         // We failed to load our baseline locally -- we're hosed.
  1426.         LogWrite("Failed to load baseline.");
  1427.         if(typeof e == "number"){
  1428.             callback(e);
  1429.         } else {
  1430.             callback(4);
  1431.         }
  1432.         return;
  1433.     }
  1434.  
  1435.     funcLoaded();
  1436. }
  1437.  
  1438. Syncd2SyncEngine.prototype._completeTransaction = function(ns, sco, callback) {
  1439.     var self = this;
  1440.  
  1441.     var funcFinishUp = function(status) {
  1442.         if (self._responseOK(status, callback)) {
  1443.             self._baseline = ns;
  1444.             self._baseline.hash = gSettings.hash;
  1445.             self._baseline.currentRevision = sco.toprev;
  1446.             self._baseline.SaveToFile(funcFinishedWrite);
  1447.         }
  1448.     };
  1449.     var funcFinishedWrite = function(status) {
  1450.         if (self._responseOK(status, callback)) {
  1451.             // gSettings.currentRevision = sco.toprev;
  1452.             gSettings.SetSyncRevision(self._datasource.syncType, sco.toprev);
  1453.             callback(0);
  1454.         }
  1455.     };
  1456.  
  1457.     ns.Declone(funcFinishUp);
  1458. }
  1459.  
  1460. Syncd2SyncEngine.prototype.getProfileNames = function(callback) {
  1461.     var self = this;
  1462.     var funcFetched = function(response) {
  1463.         if (self._responseOK(response, callback)) {
  1464.             callback(0, response);
  1465.         }
  1466.     }
  1467.  
  1468.     //SetProgressMessage("progress.gettingprofilenames");
  1469.     var request = new Request("POST",
  1470.         { path: "/user/profiles/getnames",
  1471.           host: gSettings.acctMgrHost },
  1472.         this._args({}) );
  1473.     request.Start(funcFetched);
  1474.  
  1475. }
  1476.  
  1477. OwnSyncEngine.prototype.upload = function(callback) {
  1478.     var self = this;
  1479.     var ns = new Nodeset(self._datasource);
  1480.     var token = Date.now().toString(16);
  1481.  
  1482.     // if we are doing a force upload, then that trumps forcedMerge
  1483.     if(gSettings.mustUpload(self._datasource.syncType)){
  1484.         gSettings.setMustMerge(self._datasource.syncType, false);
  1485.     }
  1486.     // even though the user says upload, we don't want
  1487.     // to do that for passwords during initial sync
  1488.     if(gSettings.mustMerge(self._datasource.syncType)){
  1489.         LogWrite("Forced Merge for Passwords");
  1490.         self.merge(true, function(response){
  1491.             if(self._responseOK(response, callback)){
  1492.                 gSettings.setMustMerge(self._datasource.syncType, false);
  1493.                 callback(response);
  1494.             }
  1495.         });
  1496.         return;
  1497.     }
  1498.  
  1499.  
  1500.     var funcFetched = function(status) {
  1501.         if (self._responseOK(status, callback)) {
  1502.             ns.ProvideCommandset(funcContinue);
  1503.         }
  1504.     };
  1505.     var funcContinue = function(status, cs) {
  1506.         if (self._responseOK(status, callback)) {
  1507.             self.request = new Request("PUT", 
  1508.                 gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType),
  1509.                 { commands: cs, token: token }, false, null, true );
  1510.             self.request.Start(funcDone);
  1511.         }
  1512.     };
  1513.     var funcDone = function(response) {
  1514.         if (self._responseOK(response, callback)) {
  1515.             gSettings.setMustUpload(self._datasource.syncType, false);
  1516.             self._completeTransaction(ns, 
  1517.                 { etag: response.etag, token: token }, callback);
  1518.         }
  1519.     };
  1520.  
  1521.     //SetProgressMessage("progress.writing");
  1522.     ns.FetchFromNative(funcFetched);
  1523. }
  1524.  
  1525. OwnSyncEngine.prototype.verifypin = function(pin, callback) {
  1526.     var self = this;
  1527.     var ns;
  1528.  
  1529.     var funcDone = function(response) {
  1530.         var funcFinished = function(resp) {
  1531.             if (self._responseOK(resp, callback)) {
  1532.                 self._completeTransaction(ns, 
  1533.                     { etag: response.etag, token: response.token }, callback);
  1534.             }
  1535.         };
  1536.         if (self._responseOK(response, callback)) {
  1537.             ns = new Nodeset(self._datasource);
  1538.             var cs = new Commandset(response.commands);
  1539.             try {
  1540.                 cs.execute(ns);
  1541.             } catch (e) {
  1542.                 LogWrite("execute failed: " + e.toSource());
  1543.                 if(typeof e == "number"){
  1544.                     callback(e);
  1545.                 } else {
  1546.                     callback(4);
  1547.                 }
  1548.                 return;
  1549.             }
  1550.  
  1551.            var result = self._datasource.verifyPin(pin, ns.Node(NODE_ROOT)); 
  1552.            callback(result ? 0 : 100);
  1553.         }
  1554.     };
  1555.     //SetProgressMessage("progress.verifying");
  1556.     LogWrite("Verifying PIN (Own Server)");
  1557.     self.request = new Request("GET", gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
  1558.     self.request.Start(funcDone);
  1559. }
  1560.  
  1561. OwnSyncEngine.prototype.download = function(callback) {
  1562.     var self = this;
  1563.     var ns;
  1564.  
  1565.     // resetPIN trumps download (only occurs in the case of
  1566.     // resetPIN in setup dialog)
  1567.     if(gSettings.mustUpload(self._datasource.syncType)){
  1568.         LogWrite("Forced Upload: Pin Reset");
  1569.         self.upload(function(response){
  1570.             if(self._responseOK(response, callback)){
  1571.                 gSettings.setMustUpload(self._datasource.syncType, false);
  1572.                 callback(response);
  1573.             }
  1574.         });
  1575.         return;
  1576.     }
  1577.     else if(gSettings.mustMerge(self._datasource.syncType)){
  1578.         LogWrite("Forced Merge for Passwords");
  1579.         self.merge(true, function(response){
  1580.             if(self._responseOK(response, callback)){
  1581.                 gSettings.setMustMerge(self._datasource.syncType, false);
  1582.                 callback(response);
  1583.             }
  1584.         });
  1585.         return;
  1586.     }
  1587.  
  1588.     var funcDone = function(response) {
  1589.         var funcFinished = function(resp) {
  1590.             if (self._responseOK(resp, callback)) {
  1591.                 self._completeTransaction(ns, 
  1592.                     { etag: response.etag, token: response.token }, callback);
  1593.             }
  1594.         };
  1595.         if (self._responseOK(response, callback)) {
  1596.             ns = new Nodeset(self._datasource);
  1597.             var cs = new Commandset(response.commands);
  1598.             try {
  1599.                 cs.execute(ns);
  1600.             } catch (e) {
  1601.                 LogWrite("execute failed: " + e.toSource());
  1602.                 if(typeof e == "number"){
  1603.                     callback(e);
  1604.                 } else {
  1605.                     callback(4);
  1606.                 }
  1607.                 return;
  1608.             }
  1609.             //SetProgressMessage("progress.loading");
  1610.             ns.FlushToNative(funcFinished);
  1611.         }
  1612.     };
  1613.  
  1614.     //SetProgressMessage("progress.downloading");
  1615.     self.request = new Request("GET", gSettings.getUrlWithUsernameAndPassword(self._datasource.syncType));
  1616.     self.request.Start(funcDone);
  1617. }
  1618.  
  1619. OwnSyncEngine.prototype._completeTransaction = 
  1620.         function(ns, sco, callback) {
  1621.     var self = this;
  1622.  
  1623.     var funcFinishUp = function(status) {
  1624.         if (self._responseOK(status, callback)) {
  1625.             self._baseline = ns;
  1626.             self._baseline.hash = gSettings.hash;
  1627.             self._baseline.SaveToFile(funcFinishedWrite);
  1628.         }
  1629.     };
  1630.     var funcFinishedWrite = function(status) {
  1631.         if (self._responseOK(status, callback)) {
  1632.             gSettings.setToken(self._datasource.syncType, sco.token);
  1633.             if (sco.etag) gSettings.setEtag(self._datasource.syncType, sco.etag);
  1634.             gSettings.SyncComplete(self._datasource.syncType);
  1635.             callback(0);
  1636.         }
  1637.     };
  1638.     ns.Declone(funcFinishUp);
  1639. }
  1640.  
  1641.  
  1642.